﻿// ***********************************************************************
// Assembly         : Tx.OCC
// Author           : tianteng
// Created          : 03-09-2021
//
// Last Modified By : tianteng
// Last Modified On : 01-26-2021
// ***********************************************************************
// <copyright file="OccGeom2dEx.cs" company="Tobias Schachte">
//     Copyright © 2015-2021 Tobias Schachte
// </copyright>
// <summary></summary>
// ***********************************************************************
using System;
using Occt;

namespace Tx.OCC
{
    /// <summary>
    /// Class OccGeom2dEx.
    /// </summary>
    public static class OccGeom2dEx
    {
        /// <summary>
        /// Finds the ellipse center from endpoints.
        /// </summary>
        /// <param name="startPnt">The start PNT.</param>
        /// <param name="endPnt">The end PNT.</param>
        /// <param name="rX">The r x.</param>
        /// <param name="rY">The r y.</param>
        /// <param name="angle">The angle.</param>
        /// <param name="sense">if set to <c>true</c> [sense].</param>
        /// <returns>gp_Pnt2d.</returns>
        public static gp_Pnt2d FindEllipseCenterFromEndpoints(gp_Pnt2d startPnt, gp_Pnt2d endPnt, double rX, double rY, double angle, bool sense)
        {
            // https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
            // F.6.5.1
            var matRotation = new gp_Mat2d();
            matRotation.SetRotation(-angle);
            var v1 = new gp_XY(startPnt.X - endPnt.X, startPnt.Y - endPnt.Y)
                .Multiplied(0.5)
                .Multiplied(matRotation);

            // F.6.5.2
            var denom = rX.Sqr() * v1.Y.Sqr() + rY.Sqr() * v1.X.Sqr();
            var numer = rX.Sqr() * rY.Sqr() - denom;

            // This correction is used in SvgNet, no idea what it is doing. 
            //double root = 0;
            //if (numer < 0)
            //{
            //    var s = (float)Math.Sqrt(1.0 - numer / (rX.Sqr() * rY.Sqr()));
            //    rX *= s;
            //    rY *= s;
            //    root = 0.0;
            //}
            //else
            //{
            //    root = Math.Sqrt(numer / denom) * (sense ? -1 : 1);
            //}

            var root = Math.Sqrt(Math.Abs(numer / denom)) * (sense ? -1 : 1);
            var c1 = new gp_XY(rX * v1.Y / rY, -(rY * v1.X / rX)).Multiplied(root);

            // F.6.5.3
            matRotation.SetRotation(angle);
            var c = new gp_XY(startPnt.X + endPnt.X, startPnt.Y + endPnt.Y)
                .Multiplied(0.5)
                .Added(c1.Multiplied(matRotation));

            return c.ToPnt();
        }



        /// <summary>
        /// Ellipticals the arc to bezier.
        /// </summary>
        /// <param name="ellipse">The ellipse.</param>
        /// <param name="startParam">The start parameter.</param>
        /// <param name="endParam">The end parameter.</param>
        /// <param name="maxInterval">The maximum interval.</param>
        /// <returns>System.ValueTuple&lt;gp_Pnt2d, gp_Pnt2d, gp_Pnt2d, gp_Pnt2d&gt;[].</returns>
//         public static (gp_Pnt2d p1, gp_Pnt2d p2, gp_Pnt2d c1, gp_Pnt2d c2)[]
//             EllipticalArcToBezier(gp_Elips2d ellipse, double startParam, double endParam, double maxInterval = Math.PI / 3)
//         {
//             if (endParam < startParam)
//                 endParam += 2 * Math.PI;
// 
//             var paramInterval = endParam - startParam;
//             var itCount = (int)Math.Ceiling(Math.Abs(paramInterval) / maxInterval);
//             var result = new ValueTuple<gp_Pnt2d, gp_Pnt2d, gp_Pnt2d, gp_Pnt2d>[itCount];
//             var itParamStep = paramInterval / itCount;
//             var itStartParam = startParam;
//             for (int step = 0; step < itCount; step++)
//             {
//                 var itEndParam = itStartParam + itParamStep;
// 
//                 // http://www.spaceroots.org/documents/ellipse/node22.html
//                 // https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/
// 
//                 ElCLib.D1(itStartParam, ellipse, out gp_Pnt2d p1, out gp_Vec2d v1);
// 
//                 ElCLib.D1(itEndParam, ellipse, out gp_Pnt2d p2, out gp_Vec2d v2);
// 
//                 var a = 3 * Math.Pow(Math.Tan((itEndParam - itStartParam) / 2), 2);
//                 var b = (Math.Sqrt(4 + a) - 1) / 3;
//                 var alpha = Math.Sin(itEndParam - itStartParam) * b;
//                 var cp1 = p1 + v1.Multiplied(alpha);
//                 var cp2 = p2 - v2.Multiplied(alpha);
// 
//                 result[step] = (p1, p2, cp1, cp2);
//                 itStartParam = itEndParam;
//             }
//             return result;
//         }

        /// <summary>
        /// Makes the ellipse.
        /// </summary>
        /// <param name="center">The center.</param>
        /// <param name="p1">The p1.</param>
        /// <param name="p2">The p2.</param>
        /// <param name="sense">if set to <c>true</c> [sense].</param>
        /// <returns>gp_Elips2d.</returns>
        public static gp_Elips2d MakeEllipse(gp_Pnt2d center, gp_Pnt2d p1, gp_Pnt2d p2, bool sense = true)
        {
            try
            {
                if (p1.Distance(p2) <= 0 || p2.Distance(center) <= 0 || p1.Distance(center) <= 0)
                    return null;

                // Make sure that p1 is the end of the major axis
                if (p1.Distance(center) < p2.Distance(center))
                {
                    var ptemp = p2;
                    p2 = p1;
                    p1 = ptemp;
                }

                // The radius a is defined by distance to point p1
                double a = center.Distance(p1);

                // Normalize ellipse to x-axis running through p1, center is in (0,0)
                double rotAngle = new gp_Ax2d(center, new gp_Vec2d(center, p1).ToDir()).Angle(new gp_Ax2d(center, gp_Dir2d.DX));
                var p2Norm = p2.Translated(center, gp_Pnt2d.Origin).Rotated(gp_Pnt2d.Origin, rotAngle);

                // The ellipse formular is:
                // x²/a² + y²/b² = 1
                // y²/b² = 1 - x²/a²
                // b²/y² = 1 / (1 - x²/a²)
                // b² = y² / (1 - x²/a²)
                double x2 = Math.Pow(p2Norm.X, 2);
                double y2 = Math.Pow(p2Norm.Y, 2);
                double a2 = a * a;
                double b = Math.Sqrt(y2 / (1 - x2 / a2));

                //Debug.WriteLine("a={0}    b={1}", a, b);

                if (double.IsNaN(a) || double.IsNaN(b))
                    return null;

                var ellipse = new gce_MakeElips2d(new gp_Ax2d(center, new gp_Dir2d(new gp_Vec2d(center, p1))), a, b).Value;

                return ellipse;
            }
            catch (Exception)
            {
                return null;
            }
        }
    }
}